package com.masonluo.mlonlinejudge.service.impl;

import cn.hutool.core.lang.Assert;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.masonluo.mlonlinejudge.dao.AcRecordRepository;
import com.masonluo.mlonlinejudge.dao.ResultRepository;
import com.masonluo.mlonlinejudge.dao.SolutionRepository;
import com.masonluo.mlonlinejudge.dao.SolutionResultJoinTestRepository;
import com.masonluo.mlonlinejudge.entity.*;
import com.masonluo.mlonlinejudge.enums.ExecResult;
import com.masonluo.mlonlinejudge.exceptions.HttpRequestFailureException;
import com.masonluo.mlonlinejudge.exceptions.ResourceNotFoundException;
import com.masonluo.mlonlinejudge.exceptions.ServerProcessException;
import com.masonluo.mlonlinejudge.mapper.SolutionMapper;
import com.masonluo.mlonlinejudge.mapper.TestCaseResultMapper;
import com.masonluo.mlonlinejudge.model.bo.SolutionBo;
import com.masonluo.mlonlinejudge.model.compile.factory.LanguageConfigUtils;
import com.masonluo.mlonlinejudge.model.dto.JudgeParamDto;
import com.masonluo.mlonlinejudge.model.dto.SolutionDto;
import com.masonluo.mlonlinejudge.model.judge.JudgeResult;
import com.masonluo.mlonlinejudge.model.judge.ResultData;
import com.masonluo.mlonlinejudge.model.mq.JudgeMessage;
import com.masonluo.mlonlinejudge.model.param.ProblemParam;
import com.masonluo.mlonlinejudge.model.vo.JudgeResultVo;
import com.masonluo.mlonlinejudge.service.*;
import com.masonluo.mlonlinejudge.utils.RedisUtil;
import com.masonluo.mlonlinejudge.utils.StringUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;


import java.util.*;

/**
 * @author masonluo
 * @date 2021/2/6 2:08 下午
 */
@Service
public class SolutionServiceImpl implements SolutionService {

    @Autowired
    private TokenService tokenService;

    @Autowired
    private UserService userService;

    @Autowired
    private SolutionMapper solutionMapper;

    @Autowired
    private TestCaseResultMapper testCaseResultMapper;

    @Autowired
    private AcRecordRepository acRecordRepository;

    @Autowired
    private SolutionRepository solutionRepository;

    @Autowired
    private ResultRepository resultRepository;

    @Autowired
    private SolutionResultJoinTestRepository solutionResultJoinTestRepository;

    @Autowired
    private TestCaseResultService testCaseResultService;

    @Autowired
    private RocketMQService rocketMQService;


    @Value("${mq.rocket.judge.topic}")
    private String topic;

    @Autowired
    private ObjectMapper json;

    @Autowired
    private ProblemService problemService;

    @Autowired
    private JudgeService judgeService;

    @Autowired
    private ProgramAnswerService programAnswerService;

    @Autowired
    private RedisUtil redisUtil;


    //1M的大小
    private final int _1M = 1024 * 1024;

    /**
     * 进行代码的评判
     */
    @Override
    public void judge(JudgeMessage param) {
        Solution solution = solutionRepository.selectById(param.getSolutionId());
        JudgeParamDto dto = buildJudgeParamDto(solution);
        //将内存单位从MB转换为B
        dto.setMaxMemory(dto.getMaxMemory() * _1M);
        // 评判进行重试的次数
        int retryCount = 3;
        JudgeResult result = null;
        Integer examAndExperimentId = param.getExamAndExperimentId();
        while (retryCount-- > 0) {
            try {
                result = judgeService.judge(dto);
                if (result == null) {
                    continue;
                }
                // 评判成功，入库，并且跳出
                result.setTestCaseId(dto.getTestCaseId());
//                handleJudgeResult(result, solution);
//                方法重载
                handleJudgeResult(result, solution, param.getExamAndExperimentId());
                return;
            } catch (HttpRequestFailureException | JsonProcessingException e) {
                // 以后记录异常，用日志框架
                e.printStackTrace();
            }
        }
//        评判失败
        Integer resultId = handleJudgeFail(solution);

        if (examAndExperimentId != 0) {
            insertResultJoinTest(solution.getUserId(), examAndExperimentId > 0 ? 1 : 0, Math.abs(examAndExperimentId), solution.getProblemId(), solution.getId(), resultId);
        }else{
            // TODO 实现了华山论剑的记录提交历史功能
            insertResultJoinTest(solution.getUserId(), 0, 0, solution.getProblemId(), solution.getId(), resultId);
        }
//        进入评判失败切面
        if (examAndExperimentId > 0) {
//            考试
//            存入缓存
            getSolutionServiceImpl().judgeFailAspect(solution, examAndExperimentId);
        }
    }

    private void insertResultJoinTest(Integer userId, Integer mode, Integer examAndExperimentId, Integer problemId, Integer solutionId, Integer resultId) {
        solutionResultJoinTestRepository.insertResultJoinTest(userId, mode, Math.abs(examAndExperimentId), problemId, solutionId, resultId);
    }


    /**
     * 切面调用方法对象
     * 强制获取代理对象，必须开启exposeProxy配置，否则获取不到当前代理对象
     *
     * @return
     */
    private SolutionServiceImpl getSolutionServiceImpl() {
        return AopContext.currentProxy() != null ? (SolutionServiceImpl) AopContext.currentProxy() : this;
    }


    @Override
    public void judgeFailAspect(Solution solution, Integer examId) {
//        带参进入评判失败切面函数
    }

    /**
     * 进行解决方案的保存，返回解决方法的id，之后用户可以使用这个id进行结果的查询
     * TODO 登录接口
     */
    @Override
    public SolutionBo save(SolutionDto solutionDto) {
//        Token token = tokenService.convert(solutionDto.getUserToken());
//        Integer userId = userService.findIdByUsername(token.getUsername());
        Integer userId = solutionDto.getUserId();
        Solution solutionDo = solutionMapper.dtoToDo(solutionDto);
        solutionDo.setUserId(userId);
        solutionRepository.insert(solutionDo);
        if (solutionDo.getId() == null) {
            throw new ServerProcessException("Can't save the solution, please try again");
        }
        SolutionBo bo = new SolutionBo();
        bo.setExamAndExperimentId(solutionDto.getExamAndExperimentId());
        bo.setId(solutionDo.getId());
        bo.setProblemId(solutionDo.getProblemId());
        bo.setUserId(solutionDo.getUserId());
        return bo;
    }

    @Override
    public SolutionBo submit(SolutionDto solutionDto) throws JsonProcessingException { // Json异常直接抛出，交由上层处理
        // 验证问题是否存在
        if (!problemService.exist(solutionDto.getProblemId())) {
            throw new ResourceNotFoundException("The problem [" + solutionDto.getProblemId() + "] does not exist, please verify");
        }
        // 保存入库
        SolutionBo bo = save(solutionDto);
        // json化提交到队列
        JudgeMessage param = generateJudgeParam(bo);
        String paramStr = json.writeValueAsString(param);
        // 提交队列
        rocketMQService.syncSend(topic, null, StringUtils.getBytes(paramStr));
        return bo;
    }

    @Override
    public Solution findById(Integer id) {
        return solutionRepository.selectById(id);
    }

    @Override
    public int updateById(Solution solution) {
        if (solution.getId() == null) {
            throw new IllegalArgumentException("Update statement should have a id");
        }
        return solutionRepository.updateById(solution);
    }

    @Override
    public JudgeResultVo findJudgeResult(Integer solutionId) {
        Solution solution = solutionRepository.selectById(solutionId);
        if (solution == null) {
            throw new ResourceNotFoundException("The solution [" + solutionId + "] does not exist.");
        }
        return convertToJudgeResultVo(solution);
    }

    @Override
    public List<Solution> listByUserId(Integer userId) {
        // TODO 自动更改为现在登录的用户id
        List<Solution> solutions;
        if (userId == -1) {
            solutions = solutionRepository.listAll();
        } else {
            solutions = solutionRepository.listByUserId(userId);
        }
        return solutions == null ? Collections.emptyList() : solutions;
    }

    @Override
    public boolean judgeSuccessByProblemIDAndUserId(Integer experimentId, Integer problemId, Integer userId) {
        Integer successNum = solutionRepository.judgeSuccessByProblemIDAndUserId(experimentId, problemId, userId);
        if (successNum > 0) {
            return true;
        }
        return false;
    }

    @Override
    public boolean judgeDoneByProblemIDAndUserId(Integer experimentId, Integer problemId, Integer userId) {
        Integer DoneNum = solutionRepository.judgeDoneByProblemIDAndUserId(experimentId, problemId, userId);
        //没做过这道题
        if (DoneNum == 0) {
            return true;
        }
        return false;
    }

    /*@Override
    public List<Integer> listPassProblem(List<Integer> problemIds, Integer userId) {
        return solutionRepository.listPassProblem(problemIds, userId);
    }*/

    @Override
    public Integer countPassProblemByUserIdAndExperimentId(Integer userId, Integer experimentId) {
        return solutionRepository.countPassProblemByUserIdAndExperimentId(userId, experimentId);
    }

    @Override
    public String findRecentSourceCode(Integer experimentId, Integer problemId, Integer userId) {
        return solutionRepository.findRecentSourceCode(experimentId, problemId, userId);
    }

    @Override
    public int judgeSubmitNextTime(Integer userId) {
//        如果为空或者已经过期，可提交！
        if (ObjectUtils.isEmpty(redisUtil.get("SolutionNextTimeUser" + userId))) {
            redisUtil.set("SolutionNextTimeUser" + userId, 1, 60);
            return 0;
        } else {
//            否则，返回剩余时间
            return (int) redisUtil.getExpire("SolutionNextTimeUser" + userId);
        }
    }

    private JudgeResultVo convertToJudgeResultVo(Solution solution) {
        JudgeResultVo vo = new JudgeResultVo();
        if (solution.getResultId() >= 0) {
            vo.setDone(true);
            vo.setResultId(solution.getResultId());
        }
        vo.setSolutionId(solution.getId());
        vo.setProblemId(solution.getProblemId());
        return vo;
    }

    private JudgeMessage generateJudgeParam(SolutionBo bo) {
        JudgeMessage param = new JudgeMessage();
        param.setSolutionId(bo.getId());
        param.setUserId(bo.getUserId());
        param.setProblemId(bo.getProblemId());
        param.setExamAndExperimentId(bo.getExamAndExperimentId());
        param.setCreateTime(new Date());
        return param;
    }

    /**
     * 构造交给Judge Server的参数
     */
    private JudgeParamDto buildJudgeParamDto(Solution solution) {
        ProblemParam problemBo = problemService.findById(solution.getProblemId());
        // 构造对象
        JudgeParamDto paramDto = new JudgeParamDto();
        paramDto.setSrc(solution.getSourceCode());
        paramDto.setOutput(true);
        paramDto.setLanguageConfig(LanguageConfigUtils.getLanguageConfig(solution.getLanguage()));
        paramDto.setMaxMemory(problemBo.getMemoryLimit());
        paramDto.setMaxCpuTime(problemBo.getTimeLimit());
        paramDto.setTestCaseId(problemBo.getTestCaseId());
        return paramDto;
    }

    /**
     * 评判成功之后，将返回的数据封装成系统所需要的数据，并且进行返回
     */
    @SuppressWarnings("unchecked")
    private void handleJudgeResult(JudgeResult result, Solution solution) {
        if (result.getErr() == null) {
//            afterJudgeSuccess((List<ResultData>) result.getData(), solution);
            afterJudgeSuccess((List<ResultData>) result.getData(), solution, result.getTestCaseId());
        } else {
            afterCompileFail(result, solution);
        }
    }

    /**
     * 加入redis缓存，加入考试id的重载方法
     * 评判成功之后，将返回的数据封装成系统所需要的数据，并且进行返回
     */
    @SuppressWarnings("unchecked")
    private void handleJudgeResult(JudgeResult result, Solution solution, Integer examAndExperimentId) {
        if (result.getErr() == null) {
//            afterJudgeSuccess((List<ResultData>) result.getData(), solution);
            afterJudgeSuccess((List<ResultData>) result.getData(), solution, result.getTestCaseId(), examAndExperimentId);
            updateUserLevel(solution.getUserId(), solution.getProblemId());
        } else {
            afterCompileFail(result, solution, examAndExperimentId);
        }
    }

    /**
     * 判断是否需要更新用户的等级
     */
    private void updateUserLevel(Integer userId, Integer problemId) {
        if (!redisUtil.hasKey(userId.toString() + "acRecord")) {
//            从数据库查出来ac的题目id
            List<Integer> problemIdList = acRecordRepository.findByUserIdAndProblemId(userId);
//            存bitmap
            problemIdList.forEach(pid -> {
                redisUtil.setBit(userId.toString() + "acRecord", pid.longValue(), true);
            });
//            设置有效时间 7200s
            redisUtil.expire(userId.toString() + "acRecord", 7200);
        }

//            如果redis存在bitmap，找该题是否已标记
        Boolean result = redisUtil.getBit(userId.toString() + "acRecord", problemId);
        if (result == false) {
//            未标记,则标记、升级
            redisUtil.setBit(userId.toString() + "acRecord", problemId.longValue(), true);
            acRecordRepository.insertIntoAcRecord(userId, problemId);
            userService.updateUserLevel(userId);
//            设置有效时间 7200s
            redisUtil.expire(userId.toString() + "acRecord", 7200);
        }

    }

    /**
     * 编译失败之后， 保存编译失败后的结果
     */
    private void afterCompileFail(JudgeResult result, Solution solution) {
        Result res = new Result();
        res.setCompilerResult(false);
        res.setErrorData((String) result.getData());
        // 插入结果
        resultRepository.insert(res);
        // 将结果新增入Solution中
        updateResultId(solution.getId(), res.getId());
    }

    /**
     * 编译失败之后， 保存编译失败后的结果
     */
    private void afterCompileFail(JudgeResult result, Solution solution, Integer examAndExperimentId) {
        Result res = new Result();
        res.setCompilerResult(false);
        res.setErrorData((String) result.getData());
        // 插入结果
        resultRepository.insert(res);
        // 将结果新增入Solution中
        updateResultId(solution.getId(), res.getId());
        if (examAndExperimentId != 0) {
            insertResultJoinTest(solution.getUserId(), examAndExperimentId > 0 ? 1 : 0, examAndExperimentId, solution.getProblemId(), solution.getId(), res.getId());
        }else{
            // TODO 实现了华山论剑的记录提交历史功能
            insertResultJoinTest(solution.getUserId(), 0, 0, solution.getProblemId(), solution.getId(), res.getId());
        }
//        考试编程题结果编译失败切面
        if (examAndExperimentId > 0) {
            getSolutionServiceImpl().afterCompileFailAspect(solution, examAndExperimentId, (String) result.getData());
        }
    }

    @Override
    public void afterCompileFailAspect(Solution solution, Integer examId, String errorData) {
//        带参进入编译失败切面函数
        return;
    }

    /**
     * 编译成功后，保存结果，并且将测试数据的结果全部进行保存
     * <p>
     * 如果有一个测试数据不通过，那么这道题则评判为编译不通过
     */
    private void afterJudgeSuccess(List<ResultData> dataList, Solution solution) {
        Assert.notEmpty(dataList, "The result dataList should contained at lease one test case");
        int maxCpuTime = 0, maxMemory = 0;
        boolean accept = true;
        List<TestCaseResult> tcrs = new ArrayList<>();
        List<ResultData> wa = new ArrayList<>();
        for (ResultData data : dataList) {
            // 测试结果保存
            TestCaseResult testCaseResult = testCaseResultMapper.resultDataToTestCaseResult(data);
            testCaseResult.setSolutionId(solution.getId());
            tcrs.add(testCaseResult);
            // 保存运行的cpu时间
            if (data.getCpuTime() > maxCpuTime) {
                maxCpuTime = data.getCpuTime();
            }
            // 保存运行的内存
            if (data.getMemory() > maxMemory) {
                maxMemory = data.getMemory();
            }
            if (!ObjectUtils.nullSafeEquals(data.getResult(), ExecResult.SUCCESS)) {
                wa.add(data);
                accept = false;
            }
        }
        // 保存测试数据
        testCaseResultService.saveAll(tcrs);
        // 评判通过
        Result res = null;
        if (accept) {
            res = doAfterAc(maxCpuTime, maxMemory);
        } else {
            res = doAfterWa(wa);
        }
        // 保存结果
        resultRepository.insert(res);
        updateResultId(solution.getId(), res.getId());
    }

    private void afterJudgeSuccess(List<ResultData> dataList, Solution solution, String testCaseId) {
        Assert.notEmpty(dataList, "The result dataList should contained at lease one test case");
        int maxCpuTime = 0, maxMemory = 0;
        boolean accept = true;
        List<TestCaseResult> tcrs = new ArrayList<>();
        List<ResultData> wa = new ArrayList<>();
        for (ResultData data : dataList) {
            // 测试结果保存
            TestCaseResult testCaseResult = testCaseResultMapper.resultDataToTestCaseResult(data);
            testCaseResult.setSolutionId(solution.getId());
            tcrs.add(testCaseResult);
            // 保存运行的cpu时间
            if (data.getCpuTime() > maxCpuTime) {
                maxCpuTime = data.getCpuTime();
            }
            // 保存运行的内存
            if (data.getMemory() > maxMemory) {
                maxMemory = data.getMemory();
            }
            if (!ObjectUtils.nullSafeEquals(data.getResult(), ExecResult.SUCCESS)) {

                //不需要保存正确的输出答案到数据库
//                List<ProgramAnswerEntity> lists = programAnswerService.findAnswer(testCaseId);
//                if(lists!=null&&lists.size()>0){
//                    int tid = Integer.parseInt(data.getTestCase());//第几个测试用例
//                    ProgramAnswerEntity programAnswerEntity = lists.get(0);
//                    String s = programAnswerEntity.getAnswer_content();
//                    List<ProgramAnswerContent> contents = JSON.parseArray(s,ProgramAnswerContent.class);
//                    for(ProgramAnswerContent pc:contents){
//                        if(pc.getId()==tid){
//                            data.setTrueAnswer(pc.getAnswer());
//                            break;
//                        }
//                    }
//                }
                wa.add(data);
                accept = false;
            }
        }
        // 保存测试数据
        testCaseResultService.saveAll(tcrs);
        // 评判通过
        Result res = null;
        if (accept) {
            res = doAfterAc(maxCpuTime, maxMemory);
        } else {
            res = doAfterWa(wa);
        }
        // 保存结果
        resultRepository.insert(res);
        updateResultId(solution.getId(), res.getId());
    }

    private void afterJudgeSuccess(List<ResultData> dataList, Solution solution, String testCaseId, Integer examAndExperimentId) {
        Assert.notEmpty(dataList, "The result dataList should contained at lease one test case");
        int maxCpuTime = 0, maxMemory = 0;
        boolean accept = true;
        List<TestCaseResult> tcrs = new ArrayList<>();
        List<ResultData> wa = new ArrayList<>();
        for (ResultData data : dataList) {
            // 测试结果保存
            TestCaseResult testCaseResult = testCaseResultMapper.resultDataToTestCaseResult(data);
            testCaseResult.setSolutionId(solution.getId());
            tcrs.add(testCaseResult);
            // 保存运行的cpu时间
            if (data.getCpuTime() > maxCpuTime) {
                maxCpuTime = data.getCpuTime();
            }
            // 保存运行的内存
            if (data.getMemory() > maxMemory) {
                maxMemory = data.getMemory();
            }
            if (!ObjectUtils.nullSafeEquals(data.getResult(), ExecResult.SUCCESS)) {

                //不需要保存正确的输出答案到数据库
//                List<ProgramAnswerEntity> lists = programAnswerService.findAnswer(testCaseId);
//                if(lists!=null&&lists.size()>0){
//                    int tid = Integer.parseInt(data.getTestCase());//第几个测试用例
//                    ProgramAnswerEntity programAnswerEntity = lists.get(0);
//                    String s = programAnswerEntity.getAnswer_content();
//                    List<ProgramAnswerContent> contents = JSON.parseArray(s,ProgramAnswerContent.class);
//                    for(ProgramAnswerContent pc:contents){
//                        if(pc.getId()==tid){
//                            data.setTrueAnswer(pc.getAnswer());
//                            break;
//                        }
//                    }
//                }
                wa.add(data);
                // TODO
                accept = false;
            }
        }
        // 保存测试数据
        testCaseResultService.saveAll(tcrs);
        // 评判通过
        Result res = null;
        if (accept) {
            res = doAfterAc(maxCpuTime, maxMemory);
        } else {
            res = doAfterWa(wa);
        }
        // 保存结果
        resultRepository.insert(res);
        updateResultId(solution.getId(), res.getId());
        if (examAndExperimentId != 0) {
            insertResultJoinTest(solution.getUserId(), examAndExperimentId > 0 ? 1 : 0, examAndExperimentId, solution.getProblemId(), solution.getId(), res.getId());
        }else{
            // TODO 实现了华山论剑的记录提交历史功能
            insertResultJoinTest(solution.getUserId(), 0, 0, solution.getProblemId(), solution.getId(), res.getId());
        }
        if (examAndExperimentId > 0) {
            // redis切面，传入solution result examId
            getSolutionServiceImpl().afterJudgeSuccessAspect(solution, examAndExperimentId, res);
        }
    }

    @Override
    public void afterJudgeSuccessAspect(Solution solution, Integer examId, Result result) {
//        带参进入评判失败切面函数
    }

    private Result doAfterWa(List<ResultData> wa) {
        ResultData data = wa.get(0);
        Result res = new Result();
        res.setCompilerResult(true);
        res.setCpuTime(data.getCpuTime());
        res.setMemory(data.getMemory());
        res.setProcessResult(data.getResult());
//        res.setTrueAnswer(data.getTrueAnswer());
        res.setTestCaseIndex(Integer.parseInt(data.getTestCase()));
        res.setUserAnswer(data.getOutput());

        return res;
    }

    /**
     * AC之后，保存评判所使用的最大的Cpu时间和最大的内存空间
     */
    private Result doAfterAc(Integer maxCpuTime, Integer maxMemory) {
        Result res = new Result();
        res.setCpuTime(maxCpuTime);
        res.setMemory(maxMemory);
        res.setProcessResult(ExecResult.SUCCESS);
        res.setCompilerResult(true);
        return res;
    }

    /**
     * 超过评判次数之后，保存失败结果，返回给用户
     */
    private Integer handleJudgeFail(Solution solution) {
        Result res = new Result();
        res.setProcessResult(ExecResult.SYSTEM_ERROR);
        resultRepository.insert(res);
        updateResultId(solution.getId(), res.getId());
        return res.getId();
    }

    /**
     * 调用方法， 更新结果
     */
    private void updateResultId(Integer solutionId, Integer resultId) {
        Solution param = new Solution();
        param.setId(solutionId);
        param.setResultId(resultId);
        updateById(param);
    }
}
